package com.ansion.groovy;

import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;

public class GroovyMap {


    public static <K, V> String join(Map<K, V> map) {
        return mapToString(map);
    }


    public static <K, V> String join(Map<K, V> map, MapTransfer<K, V> transfer) {
        return mapToString(map, transfer);
    }

    public static <K, V> String mapToString(Map<K, V> map) {
        return mapToString(map, new MapTransfer<K, V>() {
            @Override
            public String mapToString(K key, V value) {
                return key.toString() + ":" + value.toString();
            }

            @Override
            public String separator() {
                return ",";
            }

            @Override
            public String start() {
                return "[";
            }

            @Override
            public String end() {
                return "]";
            }
        });
    }

    public static <K, V> String mapToString(Map<K, V> map, MapTransfer<K, V> transfer) {
        if (map == null) {
            //empty map
            return "nullptr";
        } else if (isEmpty(map)) {
            return "";
        }
        StringBuilder builder = new StringBuilder();

        String separator = transfer.separator();
        if (TextUtils.isEmpty(separator)) {
            separator = "";
        }

        String stringsStart = transfer.start();
        String stringsEnd = transfer.end();

        if (!TextUtils.isEmpty(stringsStart)) {
            builder.append(stringsStart);
        }

        for (Map.Entry<K, V> entry : map.entrySet()) {
            builder.append(transfer.mapToString(entry.getKey(), entry.getValue()));
            builder.append(separator);
        }

        String message = builder.substring(0, builder.length() - separator.length());

        if (TextUtils.isEmpty(stringsEnd)) {
            return message;
        } else {
            return message + stringsEnd;
        }
    }

    public static boolean isEmpty(Map<?, ?> map) {
        return map == null
                || map.isEmpty();
    }

    public static <K, V> void each(Map<K, V> map, MapEach<K, V> each) {
        if (isEmpty(map)) {
            return;
        }

        for (Map.Entry<K, V> entry : map.entrySet()) {
            each.each(entry.getKey(), entry.getValue());
        }
    }

    public static <K, V> Map<K, V> filter(@Nullable Map<K, V> map, @NonNull MapItemFilter<K, V> filter) {
        if (isEmpty(map)) {
            return null;
        }

        HashMap<K, V> hashMap = new HashMap<>();
        for (Map.Entry<K, V> entry : map.entrySet()) {
            boolean add = filter.filter(entry.getKey(), entry.getValue());
            if (add) {
                hashMap.put(entry.getKey(), entry.getValue());
            }
        }
        return hashMap;
    }

    @Nullable
    public static <K, V, R> Map<K, R> collect(@Nullable Map<K, V> map, @NonNull MapCollectTransform<V, R> collectTransform) {
        if (map == null) {
            return null;
        }
        if (map.isEmpty()) {
            return new HashMap<>();
        }


        HashMap<K, R> newMap = new HashMap<>();

        each(map, (key, value) -> {
            R result = collectTransform.transform(value);
            if (result != null) {
                newMap.put(key, result);
            }
        });

        return newMap;
    }

    @Nullable
    public static <K, V> Map.Entry<K, V> find(@Nullable Map<K, V> map, @NonNull MapFinder<K, V> finder) {
        if (isEmpty(map)) {
            return null;
        }

        for (Map.Entry<K, V> entry : map.entrySet()) {
            boolean find = finder.find(entry.getKey(), entry.getValue());
            if (find) {
                return entry;
            }
        }

        return null;
    }

    @Nullable
    public static <K, V> Map<K, V> findAll(@Nullable Map<K, V> map, @NonNull MapFinder<K, V> finder) {
        if (isEmpty(map)) {
            return null;
        }

        HashMap<K, V> newMap = new HashMap<>();

        for (Map.Entry<K, V> entry : map.entrySet()) {
            boolean find = finder.find(entry.getKey(), entry.getValue());
            if (find) {
                newMap.put(entry.getKey(), entry.getValue());
            }
        }
        return newMap;
    }

    public static <K, V> boolean every(@Nullable Map<K, V> map, @NonNull MapElementCondition<K, V> condition) {
        if (isEmpty(map)) {
            return false;
        }

        for (Map.Entry<K, V> entry : map.entrySet()) {
            boolean cond = condition.condition(entry.getKey(), entry.getValue());
            if (!cond) {
                return false;
            }
        }
        return true;
    }

    public static <K, V> boolean any(@Nullable Map<K, V> map, @NonNull MapElementCondition<K, V> condition) {
        if (isEmpty(map)) {
            return false;
        }

        for (Map.Entry<K, V> entry : map.entrySet()) {
            boolean cond = condition.condition(entry.getKey(), entry.getValue());
            if (cond) {
                return true;
            }
        }
        return false;
    }

    @Nullable
    public static <K, V> Map<K, V> asMap(Map.Entry<K, V>... entries) {
        if (entries == null) {
            return null;
        }
        if (entries.length <= 0) {
            return new HashMap<>();
        }
        HashMap<K, V> map = new HashMap<>(entries.length);
        for (Map.Entry<K, V> entry : entries) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }

    public static <K, V> Map.Entry<K, V> asEntry(K key, V value) {
        return new AbstractMap.SimpleEntry<>(key, value);
    }

    public interface MapEach<K, V> {
        void each(@Nullable K key, @Nullable V value);
    }

    public interface MapItemFilter<K, V> {
        boolean filter(@Nullable K key, @Nullable V value);
    }

    public interface MapCollectTransform<V, R> {
        @Nullable
        R transform(@Nullable V value);
    }

    public interface MapFinder<K, V> {
        boolean find(@Nullable K key, @Nullable V value);
    }

    public interface MapElementCondition<K, V> {
        boolean condition(@Nullable K key, @Nullable V value);
    }


    public interface MapTransfer<K, V> {
        @NonNull
        String mapToString(K key, V value);

        @NonNull
        String separator();

        @Nullable
        String start();

        @Nullable
        String end();
    }
}
